import {
  TEqualFunc,
} from '../utils/tools';
import {
  IRemoveAtReturn,
  IPushNodeReturn,
  IInsertReturn,
  TElements,
  LinkedNodeBase,
  LinKedListNoOrder,
  LinkedNodeDoubly,
  CircularLinkedListBase,
} from './base';

export {
  SortedLinkedList,
} from './linkeedListExpand';

export class LinkedListSingly extends LinKedListNoOrder<LinkedNodeBase> {
  public constructor(elements?: TElements, equalFunc?: TEqualFunc) {
    super(LinkedNodeBase, elements, equalFunc);
  }
  
  public push(element) {
    super.push(element);
  }

  public removeAt(index: number) {
    const valueReturn = super.removeAt(index) as (IRemoveAtReturn<LinkedNodeBase> | undefined);

    return valueReturn?.nodeRemove?.value;
  }

  public remove(element) {
    const index: number = this.indexOf(element);
    return this.removeAt(index);
  }

  public insert(element: any, index: number) {
    return !!super.insert(element, index);
  }
}

export class LinkedListDoubly extends LinKedListNoOrder<LinkedNodeDoubly> {
  protected tail: TTNulish<LinkedNodeDoubly>;

  public constructor(elements?: TElements, equalFunc?: TEqualFunc) {
    super(LinkedNodeDoubly, elements, equalFunc);
  }

  public push(element) {
    const { newNode, preNode, } = super.push(element) as IPushNodeReturn<LinkedNodeDoubly>;
    
    newNode && (newNode.previous = preNode);

    this.tail = newNode;
  }

  public removeAt(index: number) {
    let result: TTNulish<LinkedNodeDoubly>;
    const valueReturn = super.removeAt(index);

    if (valueReturn) {
      const { nodeRemove, preNode, } = valueReturn as IRemoveAtReturn<LinkedNodeDoubly>;
      result = nodeRemove;
      preNode?.next && (preNode.next.previous = nodeRemove?.previous);
    }

    return result;
  }

  public getTail() {
    return this.tail?.value;
  }

  public remove(element) {
    const index: number = this.indexOf(element);
    return this.removeAt(index);
  }

  public insert(element: any, index: number) {
    const valueReturn = super.insert(element, index);

    if (valueReturn) {
      const { newNode, nextNode, preNode } = valueReturn as IInsertReturn<LinkedNodeDoubly>;

      newNode && (newNode.previous = preNode);
      nextNode && (nextNode.previous = newNode);
    }

    return !!valueReturn;
  }

  public clear() {
    super.clear();
    this.tail = null;
  }
}

export class CircularLinkedListSingly extends CircularLinkedListBase<LinkedNodeBase> {
  public constructor(elements: TElements = [], equalFunc?: TEqualFunc) {
    super(LinkedNodeBase, elements, equalFunc);
  }

  public push(element) {
    super.push(element);
  }

  public remove(element) {
    const index: number = this.indexOf(element);
    return this.removeAt(index);
  }

  public removeAt(index: number) {
    const valueReturn = super.removeAt(index) as (IRemoveAtReturn<LinkedNodeBase> | undefined);
    return valueReturn?.nodeRemove?.value;
  }

  public insert(element: any, index: number) {
    const valueReturn = super.insert(element, index);
    return !!valueReturn;
  }
}

export class CircularLinkedListDoubly extends CircularLinkedListBase<LinkedNodeDoubly> {
  protected tail: TTNulish<LinkedNodeDoubly>;
  public constructor(elements: TElements = [], equalFunc?: TEqualFunc) {
    super(LinkedNodeDoubly, elements, equalFunc);
  }

  public push(element) {
    const { newNode, preNode, } = super.push(element) as IPushNodeReturn<LinkedNodeDoubly>;
    
    newNode && (newNode.previous = preNode);
    this.head && (this.head.previous = newNode);
    this.tail = newNode;
  }

  public remove(element) {
    const index: number = this.indexOf(element);
    return this.removeAt(index);
  }

  public removeAt(index: number) {
    let result: TTNulish<LinkedNodeDoubly>;
    const valueReturn = super.removeAt(index);

    if (valueReturn) {
      const { nodeRemove, preNode, } = valueReturn as IRemoveAtReturn<LinkedNodeDoubly>;
      result = nodeRemove;
      preNode?.next && (preNode.next.previous = nodeRemove?.previous);
    }
    return result;
  }

  public insert(element: any, index: number) {
    const valueReturn = super.insert(element, index);

    if (valueReturn) {
      const { newNode, nextNode, preNode } = valueReturn as IInsertReturn<LinkedNodeDoubly>;

      newNode && (newNode.previous = preNode);
      nextNode && (nextNode.previous = newNode);
    }

    return !!valueReturn;
  }

  public getTail() {
    return this.tail?.value;
  }

  public clear() {
    super.clear();
    this.tail = null;
  }
}
